use std::borrow::Cow;

use axum::{
    async_trait,
    body::Bytes,
    extract::{FromRequest, Request},
    http::{HeaderValue, StatusCode},
    response::{IntoResponse, Response},
};

use crate::{
    error::{ec, new_err_msg, AppErr, ErrorWrap},
    server::{header, res::{make_response, }},
};

pub struct Message {
    pub seq: u8,
    pub msg_type: u8,
    pub cmd: Cow<'static, str>,
    pub body: Bytes,
}

impl Message {
    pub fn empty(seq: u8, msg_type: u8, cmd: &'static str) -> Self {
        Self {
            seq,
            msg_type,
            cmd: Cow::Borrowed(cmd),
            body: Bytes::new(),
        }
    }
}



pub const MSG_TYPE_NOTIFY: u8 = 0;
pub const MSG_TYPE_REQ: u8 = 1;
pub const MSG_TYPE_RES: u8 = 2;
pub const MSG_TYPE_ACK: u8 = 3;
pub const MSG_TYPE_PING: u8 = 4;
pub const MSG_TYPE_PONG: u8 = 5;
pub const MSG_TYPE_NOTIFY_ACK: u8 = 5;
pub const MSG_TYPE_UNKNOWN: u8 = 6;

#[async_trait]
impl<S: Sync + Send> FromRequest<S> for Message {
    type Rejection = AppErr;

    async fn from_request(req: Request, state: &S) -> Result<Self, Self::Rejection> {
        let seq: u8 = 0;
        let msg_type: u8 = MSG_TYPE_UNKNOWN;

        let msg_cmd = req
            .headers()
            .get(header::MSG_CMD)
            .ok_or(new_err_msg(ec::MSG_REQ_HEAD_INVALID, "no msg cmd"))?
            .to_str()
            .wrap()?
            .to_string();

        let body = Bytes::from_request(req, state).await?;

        Ok(Message {
            seq,
            msg_type,
            cmd: Cow::Owned(msg_cmd),
            body,
        })
    }
}

impl IntoResponse for Message {
    fn into_response(self) -> Response {
        make_response(|| {
            let seq = format!("{}", self.seq);
            let msg_type = format!("{}", self.msg_type);
            let mut resp = self.body.into_response();
            *resp.status_mut() = StatusCode::OK;
            resp.headers_mut().insert(header::API_RESP, header::VAL_API_OK);
            resp.headers_mut()
                .insert(header::MSG_SEQ, HeaderValue::from_str(&seq).wrap()?);
            resp.headers_mut()
                .insert(header::MSG_TYPE, HeaderValue::from_str(&msg_type).wrap()?);
            resp.headers_mut()
                .insert(header::MSG_CMD, HeaderValue::from_str(&self.cmd).wrap()?);
            Ok(resp)
        })
    }
}
